package com.example.adclient;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.StreamCorruptedException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicReference;
import android.text.format.Formatter;  

import android.content.Context;
import android.net.DhcpInfo;
import android.net.wifi.WifiManager;
import android.os.Environment;

public class NetClient {
	private byte[] user0 = new byte[] { 'u', 's', 'e', 'r', '0', '0' };
	//private byte[] ALIVE = new byte[] { 'A', 'L', 'I', 'V', 'E' };
	//private byte[] user1 = new byte[] { 'u', 's', 'e', 'r', '1', '0' };

	final static String ARCHIVE_FILE = "archive.config";

	static ArrayList<Product> _products = new ArrayList<Product>();
	OutputStream outs = null;
	InputStream ins = null;
	Socket s = null;
	FileOutputStream fstream = null;
	Context _context = null;
	int fileLength = 0;
	int fileSizeReceived = 0;
	
	public NetClient(Context context) {
		_context = context;
	}

	public void Loop() {
		user0[4]++;
		user0[5] = 0;
		
		while(true) {
			try {
				runOnce();
				// try again after a rest
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	void runOnce() {
		
		byte[] userBuff = new byte[8 + user0.length];
		userBuff[0] = 0;
		userBuff[1] = 0;
		userBuff[2] = 0;
		userBuff[3] = 0;
		userBuff[4] = 0;
		userBuff[5] = 0;
		userBuff[6] = 0;
		userBuff[7] = 5;
		System.arraycopy(user0, 0, userBuff, 8, user0.length);

		try {
			MyConfig myconfig = new MyConfig(_context);
			
			retrieveServerConfig(myconfig);
			
			String server_ip = myconfig.getServerIP();
			s = new Socket(server_ip, myconfig.getServerPort());
			outs = s.getOutputStream();
			outs.write(userBuff);
			ins = s.getInputStream();

			byte[] buff = new byte[16 * 1024];
			int receivedCount = 0;
			int buffAvailableStart = 0;
			while ((receivedCount = ins.read(buff, buffAvailableStart,
					buff.length - buffAvailableStart)) != 0) {
				// Because packages from server may be split into partitions,
				// we must read at least one whole package.
				// We assume that the length of packages from server are
				// less than buff.length/2.
				int valueLength = ByteBuffer.wrap(buff, 4, 4).getInt();
				receivedCount += buffAvailableStart;
				while (receivedCount < valueLength + 8) {
					int tmpi = ins.read(buff, receivedCount, buff.length
							- receivedCount);
					System.out.println("more read.size()=" + tmpi);
					if (tmpi > 0) {
						receivedCount += tmpi;
					} else {
						break;
					}
				}
				int processedStart = 0;
				while (processedStart < receivedCount) {
					processedStart = processData(buff, processedStart);

					int valueLength2 = ByteBuffer.wrap(buff,
							processedStart + 4, 4).getInt();
					if (processedStart + valueLength2 + 8 > receivedCount
							|| processedStart + valueLength2 + 8 > buff.length) {
						break;
					}
				}
				System.out.println(String.format("Received: %d\r\n",
						receivedCount));
				buffAvailableStart = 0;
				if (receivedCount > processedStart) {
					System.arraycopy(buff, processedStart, buff, 0,
							receivedCount - processedStart);
					buffAvailableStart = receivedCount - processedStart;
				}
			}
		} catch (Exception ex) {
			ex.printStackTrace();
		} finally {
			stop();
		}
	}
	
	public void stop() {
		try {
			if (outs != null) {
				s.shutdownOutput();
				outs.close();
				outs = null;
			}
			if (ins != null) {
				s.shutdownInput();
				ins.close();
				ins = null;
			}
			if (s != null) {
				s.close();
				s = null;
			}
			if (fstream != null) {
				fstream.close();
				fstream = null;
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void save() {
		FileOutputStream fos = null;
		ObjectOutputStream os = null;
		try {
			fos = createFileOutputStream(ARCHIVE_FILE);
			// fos = context.openFileOutput(ARCHIVE_FILE,
			// Context.MODE_PRIVATE);
			os = new ObjectOutputStream(fos);
			os.writeObject(_products);
			System.out.println("saving _products.size=" + _products.size());
		} catch (Exception ex) {
			ex.printStackTrace();
		} finally {
			try {
				if (os != null) {
					os.close();
					os = null;
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
			try {
				if (fos != null) {
					fos.close();
					fos = null;
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	@SuppressWarnings("unchecked")
	public void reload() {
		FileInputStream fis = null;
		ObjectInputStream ois = null;
		try {
			fis = createFileInputStream(ARCHIVE_FILE);
			// fis = context.openFileInput(ARCHIVE_FILE);
			ois = new ObjectInputStream(fis);
			_products = (ArrayList<Product>) ois.readObject();
			if (_products == null)
				_products = new ArrayList<Product>();
			System.out.println("reload _products.size=" + _products.size());
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (StreamCorruptedException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} finally {
			try {
				if (ois != null) {
					ois.close();
					ois = null;
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
			try {
				if (fis != null) {
					fis.close();
					fis = null;
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	public PlayInfo getProductPlayInfo(int index) {
		
		String filename = "";
		String description = "";
		if(_products != null && _products.size() > index) {
			Product pdt = _products.get(index);
			description = pdt.Description;
			String tmpFileName = pdt.AdFile;
			if (tmpFileName == null || tmpFileName.equals("")) {
				tmpFileName = pdt.Picture;
			}
			if (tmpFileName != null && !tmpFileName.equals("")) {
				int lastComma = Math.max(tmpFileName.lastIndexOf(','),
						tmpFileName.lastIndexOf('/'));
				if (lastComma >= 0) {
					filename = tmpFileName.substring(lastComma + 1);
				} else {
					filename = tmpFileName;
				}
			}
			String filepath = getFileAbsolutePath(filename);
			
			PlayInfo pi = new PlayInfo();
			pi.filepath = filepath;
			pi.description = description;
			return pi;
		}
		return null;
	}
	
	int processData(byte[] buff, int readStart)
			throws IOException {
		BaseData bd = new BaseData();
		bd.FromStream(buff, readStart);
		switch (bd.Type) {
		case DataType.DTFileHeader: {
			BaseData bdName = new BaseData();
			int position = bdName.FromStream(bd.Value, 0);
			BaseData bdLength = new BaseData();
			bdLength.FromStream(bd.Value, position);
			String filename = new String(bdName.Value, 0, bdName.Value.length);
			fileLength = Protocol.ToInt(bdLength.Value);
			fileSizeReceived = 0;
			String folder = Environment.getExternalStorageDirectory()
					.toString();
			File myDir = new File(folder + "/" + _context.getPackageName());
			myDir.mkdirs();
			File file = new File(myDir, filename);
			if (file.exists())
				file.delete();
			fstream = new FileOutputStream(file);
			// fstream = context.openFileOutput(filename, Context.MODE_PRIVATE);
			// BaseData fnbd = new BaseData(bd.Type,
			// Protocol.ACCEPTED.Length, Protocol.ACCEPTED);
			System.out.println("received opening file=" + filename);
			bd.Type = (int) DataType.DTFileHeaderAck;
			byte[] ack = bd.ToStream();
			if (outs != null)
				outs.write(ack, 0, ack.length);
		}
			break;
		case DataType.DTFile: {
			FileSlice fs = new FileSlice();
			fs.FromStream(bd.Value, 0);
			if (fstream != null) {
				fstream.write(fs.Value, 0, fs.Value.length);
				fileSizeReceived += fs.Value.length;
				// over
				if (fileSizeReceived == fileLength) {
					System.out.println("closing file");
					fstream.close();
					fstream = null;
				}
			}
			System.out.println("received writing fileSizeReceived="
					+ fileSizeReceived);
			BaseData ackbd = new BaseData((int) DataType.DTFileAck, 4,
					Protocol.ToByteArray(fs.getStartPosition()));
			byte[] fileacc = ackbd.ToStream();
			if (outs != null)
				outs.write(fileacc, 0, fileacc.length);
		}
			break;
		case DataType.DTFileEnd: {
			if (fstream != null) {
				fstream.close();
				fstream = null;
			}
			System.out.println("received closing file");
			BaseData ackend = new BaseData((int) DataType.DTFileEndAck,
					bd.Value.length, bd.Value);
			byte[] endacc = ackend.ToStream();
			if (outs != null)
				outs.write(endacc, 0, endacc.length);
		}
			break;
		case DataType.DTSettings: {
			AtomicReference<Integer> pos = new AtomicReference<Integer>();
			pos.set(0);

			_products.clear();

			while (pos.get() < bd.Value.length - 8) {
				_products.add(Protocol.Convert2Product(bd.Value, pos));
			}
			System.out.println("received setting" + ", products.size="
					+ _products.size() + ", scan=" + pos.get());
			save();
			BaseData settbd = new BaseData((int) DataType.DTSettingsAck, 0,
					null);
			byte[] settacc = settbd.ToStream();
			if (outs != null)
				outs.write(settacc, 0, settacc.length);
		}
			break;
		case DataType.DTCommand: {
			if ((new String(bd.Value)).equalsIgnoreCase(new String(
					Protocol.ACCEPTED))) {
			}
			BaseData commbd = new BaseData(bd.Type, Protocol.ACCEPTED.length,
					Protocol.ACCEPTED);
			byte[] commandacc = commbd.ToStream();
			if (outs != null)
				outs.write(commandacc, 0, commandacc.length);
		}
			break;
		case DataType.DTCommandProductShow: {
			String filename = "";
			String description = "";
			int pid = Protocol.ToInt(bd.Value);
			for (Product pdt : _products) {
				if (pdt.Id == pid) {
					description = pdt.Description;
					String tmpFileName = pdt.AdFile;
					if (tmpFileName == null || tmpFileName.equals("")) {
						tmpFileName = pdt.Picture;
					}
					if (tmpFileName != null && !tmpFileName.equals("")) {
						int lastComma = Math.max(tmpFileName.lastIndexOf(','),
								tmpFileName.lastIndexOf('/'));
						if (lastComma >= 0) {
							filename = tmpFileName.substring(lastComma + 1);
						} else {
							filename = tmpFileName;
						}
						break;
					}
				}
			}
			String filepath = getFileAbsolutePath(filename);
			// String filepath = context.getFilesDir() + "/" + filename;
			System.out
					.println("received showing" + ", pid=" + pid
							+ ", filepath=" + filepath + ", description="
							+ description);
			if (!filename.equals("") && new File(filepath).exists()) {
				MainActivity mainActivity = (MainActivity) _context;
				if (BuildConfig.DEBUG && pid % 2 == 0 && description.equals("")) {
					description = "http://www.google.com?from=cn";
				}
				mainActivity.showADonUIThread(filepath, description);
			}
		}
			break;
		case DataType.DTCommandProductAdd: {
			AtomicReference<Integer> pos = new AtomicReference<Integer>();
			pos.set(0);

			while (pos.get() < bd.Value.length - 8) {
				_products.add(Protocol.Convert2Product(bd.Value, pos));
			}
			System.out.println("received products added"
					+ ", _products.size()=" + _products.size());
			save();
		}
			break;
		case DataType.DTCommandProductRemove: {
			int pid = Protocol.ToInt(bd.Value);
			for (int i = _products.size() - 1; i >= 0; --i) {
				if (_products.get(i).Id == pid) {
					_products.remove(i);
					System.out.println("received products removed" + ", pid="
							+ pid);
				}
			}
			save();
		}
			break;
		case DataType.DTCommandProductUpdate: {
			AtomicReference<Integer> pos = new AtomicReference<Integer>();
			pos.set(0);
			ArrayList<Product> pros = new ArrayList<Product>();

			while (pos.get() < bd.Value.length - 8) {
				pros.add(Protocol.Convert2Product(bd.Value, pos));
			}
			for (Product pdt : pros) {
				for (Product tmp : _products) {
					if (tmp.Id == pdt.Id) {
						tmp.AdFile = pdt.AdFile;
						tmp.Description = pdt.Description;
						tmp.Name = pdt.Name;
						tmp.Picture = pdt.Picture;
						break;
					}
				}
			}
			System.out.println("received products updated" + ", pros.size="
					+ pros.size());
			save();
		}
			break;
		case DataType.DTString: {
			System.out.println("received string=" + new String(bd.Value));
			byte[] act = Protocol.Actived.ToStream();
			if (outs != null)
				outs.write(act, 0, act.length);
		}
			break;

		default:
			break;
		}
		return readStart + 8 + bd.Length;
	}

	String getFileAbsolutePath(String filename) {
		String folder = Environment.getExternalStorageDirectory().toString();
		String filepath = folder + "/" + _context.getPackageName() + "/"
				+ filename;
		return filepath;
	}

	FileOutputStream createFileOutputStream(String filename)
			throws FileNotFoundException {
		String folder = Environment.getExternalStorageDirectory().toString();
		File myDir = new File(folder + "/" + _context.getPackageName());
		myDir.mkdirs();
		File file = new File(myDir, filename);
		if (file.exists())
			file.delete();
		FileOutputStream fos = new FileOutputStream(file);
		return fos;
	}

	FileInputStream createFileInputStream(String filename)
			throws FileNotFoundException {
		String folder = Environment.getExternalStorageDirectory().toString();
		File myDir = new File(folder + "/" + _context.getPackageName());
		myDir.mkdirs();
		File file = new File(myDir, filename);
		FileInputStream fis = new FileInputStream(file);
		return fis;
	}

	@SuppressWarnings("deprecation")
	InetAddress getBroadcastAddress() throws IOException, InterruptedException {
	    WifiManager wifi = (WifiManager) _context.getSystemService(Context.WIFI_SERVICE);
	    DhcpInfo dhcp = wifi.getDhcpInfo();
	    // handle null somehow
	    while(dhcp == null || Formatter.formatIpAddress(dhcp.ipAddress).startsWith("192") != true) {
	    	// wait for DHCP
	    	Thread.sleep(1000);
	    	dhcp = wifi.getDhcpInfo();
	    }

	    int broadcast = (dhcp.ipAddress & dhcp.netmask) | ~dhcp.netmask;
	    byte[] quads = new byte[4];
	    for (int k = 0; k < 4; k++)
	      quads[k] = (byte) ((broadcast >> k * 8) & 0xFF);
	    return InetAddress.getByAddress(quads);
	}
	
	void retrieveServerConfig(MyConfig myconfig) throws IOException, InterruptedException {
		int udp_local_port = myconfig.getServerUDPPort() + 10000;
		int discovery_server_port = myconfig.getServerUDPPort();

		InetAddress inetaddress = getBroadcastAddress();

		DatagramSocket socket = new DatagramSocket(udp_local_port);
		socket.setBroadcast(true);
		socket.setSoTimeout(6 * 1000);
		
		ByteBuffer sendbb = ByteBuffer.allocate(8);
		sendbb.putLong(myconfig.getContainerID());
		
		DatagramPacket packet = new DatagramPacket(
				sendbb.array(), // BIG-Endian, by default
				sendbb.array().length,
				inetaddress, 
				discovery_server_port);
		while(true) {
			try {
				socket.send(packet);
		
				byte[] buf = new byte[64];
				DatagramPacket revPacket = new DatagramPacket(buf, buf.length);
				//socket.receive(revPacket);//remove the udp broadcast message // Does the client and server use the same port?
				socket.receive(revPacket);
				if(revPacket.getLength() >= 4) {
					byte[] ip = new byte[4];
					System.arraycopy(buf, 0, ip, 0, ip.length);
					InetAddress ipaddr = InetAddress.getByAddress(ip);
					myconfig.setServerIP(ipaddr.getHostAddress());
				}
				if(revPacket.getLength() >= 6) {
					byte[] buf2 = new byte[2];
					System.arraycopy(buf, 6, buf2, 0, buf2.length);
					ByteBuffer bb = ByteBuffer.wrap(buf2);
					myconfig.setServerPort(bb.getShort());
				}
				break;
			} catch (SocketTimeoutException e) {
				System.out.println("Timeout reached!!! " + e);
			}
		}
		socket.close();
	}

	public static class PlayInfo {
		public String filepath;
		public String description;
	}
}
